iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 22
0

今天要來玩玩 TopTabNavigation
除了前幾天介紹的 leanback library 幾個快速開發的 Fragment 之外
很多 App 都會把 TabLayout 的功能設計進去來切換類別,例如:愛奇藝、Google Play Store、myVideo、國外的 RedBull TV 等等,都有 TabLayout 的身影在裡面,如下圖
https://ithelp.ithome.com.tw/upload/images/20201006/20107165M4i0xMitA1.jpg

但在早期 leanback library 中是沒有 TabLayout 這個元件的,許多開發者一直向官方敲碗,因為手機版的 TabLayout 是沒有支援 Focus 的,開發者需要自己自定義元件才行
一直到今年的 Google I/O 這個 TopTabNavigation 才終於被納入 androidx 的 leanback library 中
到目前為止還在 alpha 版中,希望可以盡快進入到正式版喔~

那我們來看看如何實作吧
其實這個套件雖然是放在 leanback library 中,但其實它的呼叫方式跟開發 Mobile App 時沒什麼差別

依賴套件

implementation 'androidx.leanback:leanback-tab:1.1.0-alpha05'

這邊要注意的是它的版本是 1.1.0-alpha05 唷,因為這新的套件只有在 alpha 版才有

新增 TopTabNavigationActivity

新增一個繼承 FragmentActivity 的 TopTabNavigationActivity

加入 LeanbackTabLayout 和 LeanbackViewPager

在 activity_top_tab_navigation.xml 中加入 LeanbackTabLayout 和 LeanbackViewPager
程式碼如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".TopTabNavigationActivity">

    <androidx.leanback.tab.LeanbackTabLayout
        android:id="@+id/tab_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <androidx.leanback.tab.LeanbackViewPager
        android:id="@+id/view_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/tab_layout"/>

</RelativeLayout>

LoadData

跟先前一樣把資料撈進來吧

fun processData(){
    val jsonFileString: String = resources.openRawResource(R.raw.movielist).bufferedReader().use { it.readText() }
    if(BuildConfig.DEBUG) Log.d(TAG, "jsonFileString: $jsonFileString")
    mMovieList = Gson().fromJson(jsonFileString, MovieList::class.java)
    if(BuildConfig.DEBUG) Log.i(TAG, "mMovieList: $mMovieList")
}

建立 TopTabViewPagerAdapter

這個 ViewPager 的 Adapter 和 Mobile App 在開發時的 Adapter 是一樣的創建和呼叫方式
只是特別要注意的是 leanback library 中的 LeanbackViewPager 是 ViewPager 不是 ViewPager2 喔~
那覆寫的 getItem 回傳的 Fragment 就會是 RowsSupportFragment
完整程式碼如下

class TopTabViewPagerAdapter(fragmentManager: FragmentManager, behavior: Int, var mMovieListData: List<Data>? = null): FragmentStatePagerAdapter(fragmentManager, behavior) {

    private val TAG: String = javaClass.simpleName

    override fun getItem(position: Int): Fragment {
        if(BuildConfig.DEBUG) Log.v(TAG, "===== getItem =====")

        mMovieListData?.run {
            val category: Data = mMovieListData!![position]
            val subCategoryList: List<SubCategory>? = category.sub_categories
            if (subCategoryList != null && subCategoryList.isNotEmpty()) {
                val rowsSupportFragment: RowsSupportFragment = RowsSupportFragment()
                val rowsAdapter: ArrayObjectAdapter = ArrayObjectAdapter(ListRowPresenter())
                for ((subCategoryIndex, subCategory) in subCategoryList.withIndex()) {
                    val subCategoryName: String? = subCategory.sub_category_name
                    if (BuildConfig.DEBUG) Log.d(TAG, "subCategoryName: $subCategoryName")
                    val items: List<Item>? = subCategory.items
                    val listRowAdapter: ArrayObjectAdapter =
                        ArrayObjectAdapter(CustomCardPresenter())
                    if (items != null && items.isNotEmpty()) {
                        for (item in items) {
                            if (BuildConfig.DEBUG) Log.i(TAG, "movieName: ${item.name}")
                            listRowAdapter.add(item)
                        }
                        val header: HeaderItem = HeaderItem(0, subCategoryName)
                        rowsAdapter.add(ListRow(header, listRowAdapter))
                    }
                }
                rowsSupportFragment.adapter = rowsAdapter
                return rowsSupportFragment
            }
        }
        return RowsSupportFragment()
    }

    override fun getCount(): Int {
        if(BuildConfig.DEBUG) Log.v(TAG, "===== getCount =====")
        return if(mMovieListData!=null) mMovieListData!!.size else 0
    }

    override fun getPageTitle(position: Int): CharSequence? {
        val category: Data = mMovieListData!![position]
        val categoryName: String? = category.category_name
        if (BuildConfig.DEBUG) Log.w(TAG, "categoryName: $categoryName")
        return categoryName
    }
}

設置 LeanbackTabLayout 和 LeanbackViewPager

fun processView(){
    if(mMovieList!=null){
        mMovieListData = mMovieList!!.data
        if(mMovieListData!=null && mMovieListData!!.isNotEmpty()){
            mViewPagerAdapter = TopTabViewPagerAdapter(supportFragmentManager,
                FragmentStatePagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT, mMovieListData)
        }
    }

    if(mViewPagerAdapter!=null){
        view_pager.adapter = mViewPagerAdapter
    }
    tab_layout.setupWithViewPager(view_pager)
}

跑起來!!!

燈燈!!!怎麼掛掉了!!!
會掛是正常的不用緊張,從 Logcat 裡可以看到很長一大段的 Error
但最重要的是這段

Caused by: java.lang.IllegalArgumentException: The style on this component requires your app theme to be Theme.AppCompat (or a descendant).

這是在講說 LeanbackTabLayout 和 LeanbackViewPager 這兩個套件是依附在 AppCompat 底下的,所以我們要在 AndroidManifest.xml 裡,TopTabNavigationActivity 的部分加上 android:Theme,如下

<activity android:name=".TopTabNavigationActivity"
            android:theme="@style/Theme.AppCompat"/>

最後再跑一次看看

完成了!!

Yes

不過畫面有點不像 BrowseSupportFragment 那樣好看
我在研究看看如何修改吧
就降囉~


上一篇
Day 21 - PageRow & FragmentFactory
下一篇
Day 23 - Android TV to Google TV 搶先體驗!!!
系列文
宅經濟起飛,想當顆沙發馬鈴薯嗎??智慧電視會是未來的趨勢嗎??讓我們一探 Android TV 的神秘世界30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言